package com.shilei.system_io.v1;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @description: selector
 * @author: 81271
 * @date: 2020-08-03 18:56
 */
public class SelectorThread implements Runnable{
    //每个线程对应一个selector
    //多线程情况下，该主机，还程序的并发客户端被分配到多个selector上
    //不会存在交互问题

    Selector selector = null;
    LinkedBlockingQueue<Channel> lbq = new LinkedBlockingQueue<>();
    SelectorThreadGroup stg;

    public SelectorThread(SelectorThreadGroup selectorThreadGroup) {
        try {
            this.stg = selectorThreadGroup;
            selector = Selector.open();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        //Loop
        while (true) {
            try {
                //1.select()
//                System.out.println("before : " + Thread.currentThread().getName() + ": " + selector.keys().size());
                int nums = selector.select(); //阻塞 需要wakeup 激活
//                System.out.println("after : " + Thread.currentThread().getName() + ": " + selector.keys().size());
//                Thread.sleep(1000);
                //2.处理selectKeys
                if (nums > 0) {
                    Set<SelectionKey> selectionKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iter = selectionKeys.iterator();
                    while (iter.hasNext()) {
                        SelectionKey key = iter.next();
                        iter.remove();
                        if (key.isAcceptable()) {
                            acceptHandler(key);
                        } else if (key.isReadable()) {
                            readHandler(key);
                        } else if (key.isWritable()) {
//                            readHandler(key);
                        }
                    }
                }

                //3.处理tasks
                if (!lbq.isEmpty()) {
                    Channel c = lbq.take();
                    if (c instanceof ServerSocketChannel) {
                        System.out.println(Thread.currentThread().getName() + "   register listen");

                        ServerSocketChannel server = (ServerSocketChannel)c;
                        server.register(selector, SelectionKey.OP_ACCEPT);
                    } else if (c instanceof SocketChannel) {
                        System.out.println(Thread.currentThread().getName() + "   register client:"  + ((SocketChannel) c).getRemoteAddress());

                        SocketChannel client = (SocketChannel) c;
                        ByteBuffer buffer = ByteBuffer.allocate(4096);
                        client.register(selector, SelectionKey.OP_READ, buffer);

                    }
                }

            } catch (IOException | InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public void acceptHandler(SelectionKey key) {
        System.out.println(Thread.currentThread().getName() + "    acceptHandler....");

        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
        try {
            SocketChannel client = ssc.accept();
            client.configureBlocking(false);

            //choose a selector and register!
            stg.nextSelectorV3(client);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void readHandler(SelectionKey key) {
        System.out.println(Thread.currentThread().getName() + "     readHandler....");

        SocketChannel client = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        buffer.clear();
        int read;
        try {
            while (true) {
                read = client.read(buffer);
                if (read > 0) {
                    buffer.flip();
                    while (buffer.hasRemaining()) {
                        client.write(buffer);
                    }
                    buffer.clear();
                } else if (read == 0) {
                    break;
                } else {
                    System.out.println("client:" + client.getRemoteAddress() + "closed....");
                    client.close();
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public void setSelectorThreadGroup (SelectorThreadGroup boss) {
        this.stg = boss;
    }
}
